package fem2;

import inf.jlinalg.Array1DMatrix;
import inf.jlinalg.ArrayVector;
import inf.jlinalg.BLAM;
import inf.jlinalg.IMatrix;
import inf.jlinalg.IVector;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import math2.MutableDouble;
import fem2.enu.VariableName;

/**
 * general variable class for various constitutive model
 * 
 * @author hbui
 * 
 */
public class Variable {

	private Map<VariableName, MutableDouble> scalarVariables = new HashMap<VariableName, MutableDouble>();
	private Map<VariableName, IVector> vectorVariables = new HashMap<VariableName, IVector>();
	private Map<VariableName, IMatrix> matrixVariables = new HashMap<VariableName, IMatrix>();

	private Map<VariableName, double[]> scalarVariablesHistory = new HashMap<VariableName, double[]>();
	private Map<VariableName, ArrayList<IVector>> vectorVariablesHistory = new HashMap<VariableName, ArrayList<IVector>>();
	private Map<VariableName, ArrayList<IMatrix>> matrixVariablesHistory = new HashMap<VariableName, ArrayList<IMatrix>>();

	private Map<VariableName, Boolean> isAbleToSave = new HashMap<VariableName, Boolean>();

	private int buffSize;

	/**
	 * 
	 * @param bufferSize
	 *            size of buffer for history variable
	 */
	public Variable(int bufferSize) {
		this.buffSize = bufferSize;
	}

	/**
	 * register variable as scalar and initialize to zero
	 * 
	 * @param name
	 * @param save
	 *            set to true if you want to save the variable in buffer, set to
	 *            false if you don't want (It could help to save some memory)
	 */
	public void registerScalarVariable(VariableName name, boolean save) {
		if (scalarVariables.containsKey(name)) {
			throw new Error("duplicate variable name");
		}
		scalarVariables.put(name, new MutableDouble(0.0));
		if (save) {
			/*
			 * create history buffer
			 */
			scalarVariablesHistory.put(name, new double[buffSize]);
			isAbleToSave.put(name, true);
		} else {
			isAbleToSave.put(name, false);
		}

	}

	/**
	 * register variable as vector
	 * 
	 * @param name
	 * @param dim
	 * @param save
	 *            set to true if you want to save the variable in buffer, set to
	 *            false if you don't want. It could help to save some memory
	 */
	public void registerVectorVariable(VariableName name, int dim, boolean save) {
		if (vectorVariables.containsKey(name)) {
			throw new Error("duplicate variable name");
		}
		vectorVariables.put(name, new ArrayVector(dim));
		if (save) {
			vectorVariablesHistory.put(name, new ArrayList<IVector>());
			isAbleToSave.put(name, true);
		} else {
			isAbleToSave.put(name, false);
		}
	}

	/**
	 * register variable as matrix
	 * 
	 * @param name
	 * @param dim
	 * @param save
	 *            set to true if you want to save the variable in buffer, set to
	 *            false if you don't want. It could help to save some memory
	 */
	public void registerMatrixVariable(VariableName name, int dim, boolean save) {
		if (matrixVariables.containsKey(name)) {
			throw new Error("duplicate variable name");
		}
		matrixVariables.put(name, Array1DMatrix.createRowMajor(dim, dim));
		if (save) {
			matrixVariablesHistory.put(name, new ArrayList<IMatrix>());
			isAbleToSave.put(name, true);
		} else {
			isAbleToSave.put(name, false);
		}
	}

	/**
	 * 
	 * @param name
	 * @return the variable
	 */
	public MutableDouble getScalarVariable(VariableName name) {
		return scalarVariables.get(name);
	}

	/**
	 * 
	 * @param name
	 * @param k
	 *            if k=0, return the current variable, if k=-1, return the last
	 *            history variable,...
	 * @return the history variable
	 */
	public double getScalarVariable(VariableName name, int k) {
		if (k == 0) {
			return scalarVariables.get(name).getValue();
		} else {
			if (isAbleToSave.get(name)) {
				double[] histVars = scalarVariablesHistory.get(name);
				int ns = histVars.length;
				if (ns + k < 0)
					throw new Error("index exceed the number of history variable saved");
				else
					return histVars[ns + k];
			} else {
				throw new Error("variable name " + name + " is not allowed to save in buffer");
			}
		}
	}

	/**
	 * 
	 * @param name
	 * @return the variable
	 */
	public IVector getVectorVariable(VariableName name) {
		return vectorVariables.get(name);
	}

	/**
	 * 
	 * @param name
	 * @param k
	 *            if k=0, return the current variable, if k=-1, return the last
	 *            history variable,...
	 * @return the history variable
	 */
	public IVector getVectorVariable(VariableName name, int k) {
		if (k == 0) {
			return vectorVariables.get(name);
		} else {
			if (isAbleToSave.get(name)) {
				ArrayList<IVector> histVars = vectorVariablesHistory.get(name);
				int ns = histVars.size();
				if (ns + k < 0)
					throw new Error("index exceed the number of history variable saved");
				else
					return histVars.get(ns + k);
			} else {
				throw new Error("variable name " + name + " is not allowed to save in buffer");
			}
		}
	}

	/**
	 * 
	 * @param name
	 * @return the variable
	 */
	public IMatrix getMatrixVariable(VariableName name) {
		return matrixVariables.get(name);
	}

	/**
	 * 
	 * @param name
	 * @param k
	 *            if k=0, return the current variable, if k=-1, return the last
	 *            history variable,...
	 * @return the history variable
	 */
	public IMatrix getMatrixVariable(VariableName name, int k) {
		if (k == 0) {
			return matrixVariables.get(name);
		} else {
			if (isAbleToSave.get(name)) {
				ArrayList<IMatrix> histVars = matrixVariablesHistory.get(name);
				int ns = histVars.size();
				if (ns + k < 0)
					throw new Error("index exceed the number of history variable saved");
				else
					return histVars.get(ns + k);
			} else {
				throw new Error("variable name " + name + " is not allowed to save in buffer");
			}
		}
	}

	/**
	 * copy the variable
	 * 
	 * @param name
	 * @param var
	 */
	public void getVariable(VariableName name, IVector var) {
		BLAM.copy(getVectorVariable(name), var);
	}

	/**
	 * copy the variable
	 * 
	 * @param name
	 * @param var
	 */
	public void getVariable(VariableName name, IMatrix var) {
		BLAM.copy(getMatrixVariable(name), var);
	}

	/**
	 * save current variable to history
	 */
	public void save() {
		/*
		 * save scalar variables
		 */
		Iterator<Entry<VariableName, MutableDouble>> scalarEntries = scalarVariables.entrySet()
				.iterator();
		while (scalarEntries.hasNext()) {
			Entry<VariableName, MutableDouble> thisEntry = (Entry<VariableName, MutableDouble>) scalarEntries
					.next();
			VariableName name = thisEntry.getKey();
			if (isAbleToSave.get(name)) {
				MutableDouble value = thisEntry.getValue();
				double[] values = scalarVariablesHistory.get(name);
				/*
				 * shift back old values
				 */
				for (int i = 0; i < buffSize - 1; i++) {
					values[i] = values[i + 1];
				}
				/*
				 * add in new value
				 */
				values[buffSize - 1] = value.getValue();
			}
		}
		/*
		 * save vector variables
		 */
		Iterator<Entry<VariableName, IVector>> vectorEntries = vectorVariables.entrySet()
				.iterator();
		while (vectorEntries.hasNext()) {
			Entry<VariableName, IVector> thisEntry = (Entry<VariableName, IVector>) vectorEntries
					.next();
			VariableName name = thisEntry.getKey();
			if (isAbleToSave.get(name)) {
				IVector value = thisEntry.getValue();
				ArrayList<IVector> values = vectorVariablesHistory.get(name);
				/*
				 * check size of vector variable history
				 */
				int n = values.size();
				if (n < buffSize) {
					/*
					 * insert new value
					 */
					values.add((IVector) value.clone());
				} else {
					/*
					 * shift back old values
					 */
					for (int i = 0; i < buffSize - 1; i++) {
						BLAM.copy(values.get(i + 1), values.get(i));
					}
					BLAM.copy(value, values.get(buffSize - 1));
				}
			}
		}
		/*
		 * save matrix variables
		 */
		Iterator<Entry<VariableName, IMatrix>> matrixEntries = matrixVariables.entrySet()
				.iterator();
		while (vectorEntries.hasNext()) {
			Entry<VariableName, IMatrix> thisEntry = (Entry<VariableName, IMatrix>) matrixEntries
					.next();
			VariableName name = thisEntry.getKey();
			if (isAbleToSave.get(name)) {
				IMatrix value = thisEntry.getValue();
				ArrayList<IMatrix> values = matrixVariablesHistory.get(name);
				/*
				 * check size of vector variable history
				 */
				int n = values.size();
				if (n < buffSize) {
					/*
					 * insert new value
					 */
					values.add((IMatrix) value.clone());
				} else {
					/*
					 * shift back old values
					 */
					for (int i = 0; i < buffSize - 1; i++) {
						BLAM.copy(values.get(i + 1), values.get(i));
					}
					BLAM.copy(value, values.get(buffSize - 1));
				}
			}
		}
	}

}
